java网络-TCP/IP 三次握手与四次分手-实践

20240804224559

java网络-TCP/IP 三次握手与四次分手-实践

学习,学习一个知识就像扒洋葱,从外向里一层一层的扒。

通过上一篇 java网络-TCP/IP 三次握手与四次分手-理论 ,我们在理论上了解了 tcp/ip 的知识。

在学习的路上,能具体就不抽象,能看见就不想象,能操作就不猜想。

实践是检验真理的唯一标准。实践也是彻底学懂的不二方法。

参考理论部分 java网络-TCP/IP 三次握手与四次分手-理论 的三次握手和四次分手流程图,我们从方法调用入手,随着方法调用的进行,我们观察方法调用执行后那一时刻系统内核关于进程、socket、文件描述符、系统函数的信息

理论

三次握手流程图

20240728131348

四次分手流程图
20240728234248

场景实践

通过运行java程序观察握手过程

java - socket可运行代码

server socket
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.StandardSocketOptions;

public class SocketIOPropertites {

private static final int RECEIVE_BUFFER = 10;
private static final int SO_TIMEOUT = 0;
private static final boolean REUSE_ADDR = false;
private static final int BACK_LOG = 2;
private static final boolean CLI_KEEPALIVE = false;
private static final boolean CLI_OOB = false;
private static final int CLI_REC_BUF = 20;
private static final boolean CLI_REUSE_ADDR = false;
private static final int CLI_SEND_BUF = 20;
private static final boolean CLI_LINGER = true;
private static final int CLI_LINGER_N = 0;
private static final int CLI_TIMEOUT = 0;
private static final boolean CLI_NO_DELAY = false;

public static void main(String[] args) {
ServerSocket server = null;
try {
server = new ServerSocket();
server.bind(new InetSocketAddress( 9090), BACK_LOG);
server.setReceiveBufferSize(RECEIVE_BUFFER);
server.setReuseAddress(REUSE_ADDR);
server.setSoTimeout(SO_TIMEOUT);
} catch (IOException e) {e.printStackTrace();}
System.out.println("server up use 9090!");

while (true) {
try {
int read = System.in.read(); //分水岭:
System.out.println("read: " + read);

Socket client = server.accept();
System.out.println("client port: " + client.getPort());

client.setKeepAlive(CLI_KEEPALIVE);
client.setOOBInline(CLI_OOB);
client.setReceiveBufferSize(CLI_REC_BUF);
client.setReuseAddress(CLI_REUSE_ADDR);
client.setSendBufferSize(CLI_SEND_BUF);
client.setSoLinger(CLI_LINGER, CLI_LINGER_N);
client.setSoTimeout(CLI_TIMEOUT);
client.setTcpNoDelay(CLI_NO_DELAY);

new Thread(() -> {
while (true) {
try {
InputStream in = client.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
char[] data = new char[1024];
int num = reader.read(data); //阻塞2
if (num > 0) {
System.out.println("client read some data is :" + num + " val :" + new String(data, 0, num));
} else if (num == 0) {
System.out.println("client readed nothing!");
continue;
} else {
System.out.println("client readed -1...");
client.close();
break;
}

} catch (IOException e) {e.printStackTrace();}
}
}).start();
} catch (IOException e) {e.printStackTrace();
}finally {
try {
server.close();
} catch (IOException e) {e.printStackTrace();}
}
}
}
}
client socket
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.io.*;
import java.net.Socket;

public class SocketClient {

public static void main(String[] args) {
try {
Socket client = new Socket("127.0.0.1",9090);
client.setSendBufferSize(20);
client.setTcpNoDelay(false);
client.setOOBInline(false);

OutputStream out = client.getOutputStream();

InputStream in = System.in;
BufferedReader reader = new BufferedReader(new InputStreamReader(in));

while(true){
String line = reader.readLine();
if(line != null ){
byte[] bb = line.getBytes();
for (byte b : bb) {
out.write(b);
}
}
}
} catch (IOException e) {e.printStackTrace();}
}
}

两个 java 文件放到一个目录中,如/Users/study/tcp_ip/

1
2
-rw-r--r--  1 xxx  staff   808B  8 16 22:51 SocketClient.java
-rw-r--r-- 1 xxx staff 2.2K 8 16 22:52 SocketIOPropertites.java

运行java程序&实时观察与分析

我们首先在端口开启5个window窗口,分别叫 w1,w2,w3,w4,w5

  • w1: 运行 server socket程序: javac SocketIOPropertites.java && java SocketIOPropertites
  • w2: 运行 client socket程序: javac SocketClient.java && java SocketClient
  • w3: 执行netstat -natp|grep -e ‘9090’
  • w4: 执行lsof -p
  • w5: 执行tcpdump -nn -i eth0 port 9090

Tips

  • lsof: 查看在某一刻一个进程的所有文件描述符的信息
  • netstat/ss: 查看内核中的socket的建立过程
  • tcpdump -nn -i eth0 port xxx: 监听某个端口,抓取它的数据包

命令安装

压测工具-ab

1、客户端上安装ab压测工具:yum install httpd-tools
2、进行并发请求,请求前开启两端的抓包

1
2
3
$ ab -n 100 -c 10 http://172.23.133.138:8000/
-n 100 表示总共发送100个请求。
-c 10 表示并发数为10,也就是同时尝试建立1000个连接

三次握手-实践

第一次握手丢了-实践

第二次握手丢了-实践

第三次握手丢了-实践

四次分手-实践

第一次分手丢了-实践

第二次分手丢了-实践

第三次分手丢了-实践

第四次分手丢了-实践

半连接队列溢出-实践

全连接队列溢出-实践

辅助

解决过程中用到的资料

精讲TCPIP

代码:bjmashbing-sysio

TCP半连接全连接(一) – 全连接队列相关过程

从一次线上问题说起,详解 TCP 半连接队列、全连接队列

tcp-连接建立

tcp-3-way-handshake-process